本章开始介绍V4L2的框架。首先看一下我自己画的v4l2框架图。接下来的章节我们会逐个分析图片中的每个结构体。
video_device的首要任务就是在/dev/目录下创建设备文件。V4L2 简易框图中可以看出,video_device处于最上端。由于图片太大,将video_device相关的图片截图如下:
下面是video_device的定义:
struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
#endif
/* device ops */
const struct v4l2_file_operations *fops;
/* sysfs */
struct device dev; /* v4l device */
struct cdev *cdev; /* character device */
/* Set either parent or v4l2_dev if your driver uses v4l2_device */
struct device *parent; /* device parent */
struct v4l2_device *v4l2_dev; /* v4l2_device parent */
/* Control handler associated with this device node. May be NULL. */
struct v4l2_ctrl_handler *ctrl_handler;
/* Priority state. If NULL, then v4l2_dev->prio will be used. */
struct v4l2_prio_state *prio;
/* device info */
char name[32];
int vfl_type;
/* 'minor' is set to -1 if the registration failed */
int minor;
u16 num;
/* use bitops to set/clear/test flags */
unsigned long flags;
/* attribute to differentiate multiple indices on one physical device */
int index;
/* V4L2 file handles */
spinlock_t fh_lock; /* Lock for all v4l2_fhs */
struct list_head fh_list; /* List of struct v4l2_fh */
int debug; /* Activates debug level*/
/* Video standard vars */
v4l2_std_id tvnorms; /* Supported tv norms */
v4l2_std_id current_norm; /* Current tvnorm */
/* callbacks */
void (*release)(struct video_device *vdev);
/* ioctl callbacks */
const struct v4l2_ioctl_ops *ioctl_ops;
/* serialization lock */
struct mutex *lock;
};
video_device可以动态分配,或者静态分配(嵌入到其他结构体中)。
动态分配的方法如下:
struct video_device vdev = video_device_alloc();
if (vdev=NULL) {
return -ENOMEN;
}
vdev->release = video_device_release;
上面的代码中,最后一行是必须的。也就是说,必须为video_device指定其release函数。该函数会在video_device的最后一个用户退出后被调用,作用就是释放所申请的空间。
如果video_device是被嵌入到其他结构体中的话,那么必须为其指定我们自己的释放函数。但是在这种静态分配的情况下,一般情况下,我们是不用释放什么空间的。此时就为其指定一个空函数,例如video_device_release_empty()。
当lock不为NULL时,V4L2框架会使用此mutex确保操作的互斥性。
然而,对于某个比较耗时的操作,若在运行过程中加锁,会导致驱动无法为其他任务服务。此时,可以将lock设置为NULL,互斥操作由驱动自己负责,V4L2框架不参与。驱动编写者必须确保在调用V4L2的函数时,打开mutex,退出时关闭mutex。
prio成员变量,指向v4l2_prio_state结构体。通过该结构体V4L2实现了文件优先级的功能。
先看下v4l2_prio_state的定义:
struct v4l2_prio_state {
atomic_t prios[4];
};
V4L2有三个优先级,分别定义如下:
enum v4l2_priority {
V4L2_PRIORITY_UNSET = 0, /* not initialized */
V4L2_PRIORITY_BACKGROUND = 1,
V4L2_PRIORITY_INTERACTIVE = 2,
V4L2_PRIORITY_RECORD = 3,
V4L2_PRIORITY_DEFAULT = V4L2_PRIORITY_INTERACTIVE,
};
V4L2_PRIORITY_BACKGROUND:最低优先级,正如字面意思,主要用于后台处理,比如监视VBI数据的传输。 V4L2_PRIORITY_INTERACTIVE : 默认优先级。 V4L2_PRIORITY_RECORD :最高优先级。系统中只能有一个此优先级应用,此优先级的应用会阻塞其他所有的应用。
驱动中,是如何操作v4l2_prio_stat结构体中的4个atomic_t变量,从而实现优先级管理呢?概括之,如下:
V4L2中与优先级相关的函数有下列几个:
void v4l2_prio_init(struct v4l2_prio_state *global); : 清零所有的优先级变量。int v4l2_prio_change(struct v4l2_prio_state *global, enum v4l2_priority *local, enum v4l2_priority new); : 设置指定的优先级(new)变量的值为1,并将以前的优先级(local)变量设置为0.void v4l2_prio_open(struct v4l2_prio_state *global, enum v4l2_priority *local); : 设置程序的优先级为V4L2_PRIORITY_DEFAULTvoid v4l2_prio_close(struct v4l2_prio_state *global, enum v4l2_priority local); : 设置当前优先级变量的值为0enum v4l2_priority v4l2_prio_max(struct v4l2_prio_state *global); : 查询程序的优先级。查询方法如上面所示。int v4l2_prio_check(struct v4l2_prio_state *global, enum v4l2_priority local); : 查询以确定程序是否能在指定的优先级下工作。如可以,则返回0;不可以则返回-EBUSY。video_device的ioctl_ops和fops成员的任务就是实现将用户的操作(函数调用)”移花接木”般得映射到实际的驱动代码中。V4L2是如何实现映射的呢?首先使用fops将用户的ioctl操作映射到V4L2自己的__video_do_ioctl函数中,然后再通过ioctl_ops将具体的cmd传递给驱动中的相关函数。
下面以VIDIOC_QUERYCAP为例讲解。
static const struct file_operations v4l2_fops = { .owner = THIS_MODULE, .read = v4l2_read, .write = v4l2_write, .open = v4l2_open, .get_unmapped_area = v4l2_get_unmapped_area, .mmap = v4l2_mmap, .unlocked_ioctl = v4l2_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = v4l2_compat_ioctl32, #endif .release = v4l2_release, .poll = v4l2_poll, .llseek = no_llseek, }; vdev->cdev->ops = &v4l2_fops; vdev->cdev->owner = owner; ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); if (ret < 0) { printk(KERN_ERR "%s: cdev_add failed\n", __func__); kfree(vdev->cdev); vdev->cdev = NULL; goto cleanup; }
从上面的代码可以看出来,v4l2在注册字符设备时,将该字符设备的file_operations赋值为v4l2_fops。下面看看v4l2_ioctl的实现。
static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(filp);
int ret = -ENODEV;
if (vdev->fops->unlocked_ioctl) {
if (vdev->lock && mutex_lock_interruptible(vdev->lock))
return -ERESTARTSYS;
if (video_is_registered(vdev))
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
if (vdev->lock)
mutex_unlock(vdev->lock);
} else if (vdev->fops->ioctl) {
static DEFINE_MUTEX(v4l2_ioctl_mutex);
struct mutex *m = vdev->v4l2_dev ?
&vdev->v4l2_dev->ioctl_lock : &v4l2_ioctl_mutex;
if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m))
return -ERESTARTSYS;
if (video_is_registered(vdev))
ret = vdev->fops->ioctl(filp, cmd, arg);
if (cmd != VIDIOC_DQBUF)
mutex_unlock(m);
} else
ret = -ENOTTY;
return ret;
}
可以看到v4l2_ioctl的处理分为两种情况:unlocked_ioctl和正常的ioctl(具体区别后面在仔细讲)。不论哪种情况,都会调用vdev->fops->ioctl函数。看一下soc_camera驱动中此函数是如何实现的:
static struct v4l2_file_operations soc_camera_fops = {
.owner = THIS_MODULE,
.open = soc_camera_open,
.release = soc_camera_close,
.unlocked_ioctl = video_ioctl2,
.read = soc_camera_read,
.mmap = soc_camera_mmap,
.poll = soc_camera_poll,
};
一般情况下,unlocked_ioctl都是指向video_ioctl2函数的。那么我们接下来看看video_ioctl2的代码:
long video_ioctl2(struct file *file,
unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
video_ioctl2函数接着调用video_usercopy函数。video_usercopy函数会对用户传递过来的参数检查,将用户空间的数据拷贝到内核空间,调用我们指定的__video_do_ioctl函数,并将内核空间的数据拷贝到用户空间。
接下来终于到分析关键函数了,__video_do_ioctl :
static long __video_do_ioctl(struct file *file,
unsigned int cmd, void *arg)
{
struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
void *fh = file->private_data;
struct v4l2_fh *vfh = NULL;
int use_fh_prio = 0;
long ret_prio = 0;
long ret = -ENOTTY;
if (ops == NULL) {
printk(KERN_WARNING "videodev: \"%s\" has no ioctl_ops.\n",
vfd->name);
return ret;
}
if ((vfd->debug & V4L2_DEBUG_IOCTL) &&
!(vfd->debug & V4L2_DEBUG_IOCTL_ARG)) {
v4l_print_ioctl(vfd->name, cmd);
printk(KERN_CONT "\n");
}
if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
vfh = file->private_data;
use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
}
if (use_fh_prio)
ret_prio = v4l2_prio_check(vfd->prio, vfh->prio);
switch (cmd) {
/* --- capabilities ------------------------------------------ */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = (struct v4l2_capability *)arg;
if (!ops->vidioc_querycap)
break;
cap->version = LINUX_VERSION_CODE;
ret = ops->vidioc_querycap(file, fh, cap);
if (!ret)
dbgarg(cmd, "driver=%s, card=%s, bus=%s, "
"version=0x%08x, "
"capabilities=0x%08x, "
"device_caps=0x%08x\n",
cap->driver, cap->card, cap->bus_info,
cap->version,
cap->capabilities,
cap->device_caps);
break;
}
......
return ret;
}
由于代码太长,只节选了VIDIOC_QUERYCAP的代码。__video_do_ioctl做完参数检查后,就是判断cmd的值并作相应处理。基本上所有的case的操作都一样:判断ops结构体中所对应的函数是否有效,若有效则使用用户指定的参数调用,否则返回-ENOTTY.